package net.kem.android.positioner;

import java.io.IOException;
import java.util.List;
import java.util.Locale;

import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

/**
 * This example is mostly taken from
 * http://www.codeproject.com/Articles/112044/positioner-App-to-Find-Current-Nearest-Location-us
 *
 * @author Evgeny.Kurtser
 *
 */
public class PositionerActivity extends MapActivity {
	private MapView mapView;
	private MapController mapController;
	private LocationListener locationListener;
	private Location currentLocation;// GeoPoint
	private String currentAddress;
	private boolean isGPSWariningIssued;
	private TextView txtInPOIRadius;
	private TextView txtAccuracy;
	private TextView txtGPSDistance;
	private TextView txtGPSInterval;

	private PositionerService.LocalBinder binder;

	// Our handler for received Intents. This will be called whenever an Intent with an action named "toastEvent" is broadcasted.
	private BroadcastReceiver toastMessageReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			// Get extra data included in the Intent
			String to = intent.getStringExtra("to");
				if (to != null && (to.length()==0 || PositionerActivity.class.getName().equals(to))) {
				String message = intent.getStringExtra("message");
				if (message != null) {
					Utils.toast(getApplicationContext(), message, Toast.LENGTH_SHORT);
				}
			}
		}
	};
	// Our handler for received Intents. This will be called whenever an Intent with an action named "custom-event-name" is broadcasted.
	private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			// Get extra data included in the Intent
			Bundle bundle = intent.getBundleExtra("messageBundle");
			if (bundle != null) {
				boolean inPOIRadius = bundle.getBoolean("inPOIRadius");
				txtInPOIRadius.setText(inPOIRadius? "IN ": "OUT ");
				int currentGPSRefreshDistance = bundle.getInt("currentGPSRefreshDistance");
				txtGPSDistance.setText(String.valueOf(currentGPSRefreshDistance) + " m");
				int currentGPSRefreshInterval = bundle.getInt("currentGPSRefreshInterval");
				txtGPSInterval.setText(String.valueOf(currentGPSRefreshInterval) + " s");

				int accuracy = (int)bundle.getFloat("currentLocationAccuracy");
				int accuracyThreshold = Settings.getGPSAccuracyThreshold();
				if(accuracy > accuracyThreshold) {
					txtAccuracy.setTextColor(Color.RED);
				} else {
					txtAccuracy.setTextColor(Color.BLACK);
				}
				txtAccuracy.setText(String.valueOf(accuracy) + "m");
			}
		}
	};

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		mapView = (MapView) findViewById(R.id.mapView);
		mapView.setStreetView(true); // enable Street view by default
		// mapView.setSatellite(true); // enable to show Satellite view
		// mapView.setTraffic(true); // enable to show Traffic on map
		mapView.setBuiltInZoomControls(true);
		mapController = mapView.getController();

		// Move to pre-defined location.
		String coordinates[] = { "32.2", "34.5" };
		double lat = Double.parseDouble(coordinates[0]);
		double lng = Double.parseDouble(coordinates[1]);
		currentLocation = new Location(LOCATION_SERVICE);
		mapController.animateTo(new GeoPoint((int) (lat * 1E6), (int) (lng * 1E6)));

		mapController.setZoom(10); // Set default zoom

		// Work with GPS service
		locationListener = new GPSLocationListener();

		// Define map overlay
		MapGestureDetectorOverlay mapOverlay = new MapGestureDetectorOverlay();// new MyOnGestureListener()
		List<Overlay> listOfOverlays = mapView.getOverlays();
		listOfOverlays.clear();
		listOfOverlays.add(mapOverlay);

		registerForContextMenu(mapView);

		// Register to receive messages from PositionerService.
		// We are registering an observer (mMessageReceiver) to receive Intents
		// with actions named "custom-event-name".
		LocalBroadcastManager.getInstance(this).registerReceiver(toastMessageReceiver, new IntentFilter("toastEvent"));
		LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter("custom-event-name"));

		txtInPOIRadius = (TextView) findViewById(R.id.txtInPOIRadius);
		txtAccuracy = (TextView) findViewById(R.id.txtAccuracy);
		txtGPSDistance = (TextView) findViewById(R.id.txtGPSDistance);
		txtGPSInterval = (TextView) findViewById(R.id.txtGPSInterval);

		// Bind to LocalService
		Intent intent = new Intent(this, PositionerService.class);
		bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
		Log.d(getClass().getSimpleName(), "After onCreate");
	}

	// onUserInteraction(); See
	// http://stackoverflow.com/questions/7743976/android-activity-manager/7744354#7744354
	// http://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/

	@Override
	protected void onStart() {
		// The activity is about to become visible.
		super.onStart();
		Log.d(getClass().getSimpleName(), "After onStart");
	}

	@Override
	protected void onResume() {
		// The activity has become visible (it is now "resumed").
		super.onResume();
		Log.d(getClass().getSimpleName(), "After onResume");
	}

	@Override
	protected void onPause() {
		// Another activity is taking focus (this activity is about to be "paused").
		super.onPause();
		Log.d(getClass().getSimpleName(), "After onPause");
	}

	@Override
	protected void onStop() {
		// The activity is no longer visible (it is now "stopped")
		super.onStop();
		Log.d(getClass().getSimpleName(), "After onStop");
	}

	@Override
	protected void onDestroy() {
		// The activity is about to be destroyed.
		super.onDestroy();
		// Unbind from the service
		if(binder != null) {
			if(locationListener != null) {
				binder.removeLocationListener(locationListener);
			}
			unbindService(mConnection);
		}

		// Unregister since the activity is about to be closed.
		if(toastMessageReceiver != null) {
			LocalBroadcastManager.getInstance(this).unregisterReceiver(toastMessageReceiver);
		}
		if(mMessageReceiver != null) {
			LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
		}
		unregisterForContextMenu(mapView);
		Log.d(getClass().getSimpleName(), "After onDestroy");
	}

	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}

	/** Defines callsbacks for service binding, passed to bindService() */
	private ServiceConnection mConnection = new ServiceConnection() {
		@Override
		public void onServiceConnected(ComponentName className, IBinder service) {
			// We've bound to LocalService, cast the IBinder and get LocalService instance
			float gpsRefreshDistance = Settings.GPS_REFRESH_DISTANCE_DEFAULT;
			int gpsRefreshInterval = 30; // 30 seconds.
			binder = (PositionerService.LocalBinder) service;
			binder.addLocationListener(locationListener, gpsRefreshDistance, gpsRefreshInterval);
		}

		@Override
		public void onServiceDisconnected(ComponentName arg0) {
			binder.removeLocationListener(locationListener);
			binder = null;
		}
	};

	// ---- Context Menu BEGIN ----
	@Override
	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
		super.onCreateContextMenu(menu, v, menuInfo);
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.context, menu);
	}

	@Override
	public boolean onContextItemSelected(MenuItem item) {
		// AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
		switch(item.getItemId()) {
		case R.id.contextMenuAddPOI:
			POI poi = new POI();
			poi.setCoordinates(currentLocation);
			Intent intent = new Intent(PositionerActivity.this, POIActivity.class).putExtra(POIActivity.EXTRA_MESSAGE_POI, poi);
			startActivity(intent);
			return true;
		case R.id.contextMenuSendCurrentLocation:
			binder.sendCurrentLocation(currentLocation);
			return true;
		default: return super.onContextItemSelected(item);
		}
		/*final POI poi = Settings.getByResourceId(item.getItemId());
		if (poi != null) {
			new AlertDialog.Builder(this).setTitle("Confirmation")
			.setMessage("Do you really want to set\nPOI location\n" + poi.getName())
			.setNegativeButton("No", new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int id) {
					dialog.cancel();
				}
			}).setPositiveButton("OK", new OnClickListener() {
				public void onClick(DialogInterface dialog, int id) {
					Log.d("onContextItemSelected", poi.getName() + " at " + currentLocation.toString());
					poi.setCoordinates(currentLocation);
					poi.store();
				}
			}).show();
			return true;
		} else {
			return super.onContextItemSelected(item);
		}*/
	}

	// ---- Context Menu END ----

	// ---- System Menu BEGIN ----
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		super.onPrepareOptionsMenu(menu);
		/*MenuItem item;
		item = menu.findItem(R.id.itemHome);
		item.setEnabled(Settings.isPOIDefined("Home"));
		item = menu.findItem(R.id.itemWork);
		item.setEnabled(Settings.isPOIDefined("Work"));
		item = menu.findItem(R.id.itemSupermartket);
		item.setEnabled(Settings.isPOIDefined("Supermartket"));
		item = menu.findItem(R.id.itemBank);
		item.setEnabled(Settings.isPOIDefined("Bank"));
		item = menu.findItem(R.id.itemFriends);
		item.setEnabled(Settings.isPOIDefined("Friends"));*/
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle item selection
		/*String key;
		int locationId;*/
		switch (item.getItemId()) {
		/*case R.id.itemHome:
			key = "Home";
			locationId = 1;
			break;
		case R.id.itemWork:
			key = "Work";
			locationId = 2;
			break;
		case R.id.itemSupermartket:
			key = "Supermartket";
			locationId = 3;
			break;
		case R.id.itemBank:
			key = "Bank";
			locationId = 4;
			break;
		case R.id.itemFriends:
			key = "Friends";
			locationId = 5;
			break;*/
		case R.id.itemStoreCurrentLocation:
			openContextMenu(mapView);
			return true;
		case R.id.itemAbout:
			new AlertDialog.Builder(this).setTitle("About")
					.setMessage("GPS location sample application.\nBy Evgeny Kurtser\nlopotun@gmail.com")
					.setPositiveButton("OK", new OnClickListener() {
						public void onClick(DialogInterface arg0, int arg1) {// //do stuff onclick of YES
							arg0.dismiss();
						}
					}).show();
			return true;
		case R.id.itemSettings:
			//Intent iSA = new Intent(PositionerActivity.this, SettingsActivity.class);
			Intent iSA = new Intent(PositionerActivity.this, PreferencesFromXml.class);
			startActivity(iSA);
			return true;
		case R.id.itemGetPOIs:
			binder.sendGetAllPOIs();
			return true;
		case R.id.itemExit:
			finish();
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
		/*POI poi = Settings.getPOIByName(key);
		if (poi != null && binder != null) {
			binder.sendLocation(poi.getCoordinates(), locationId);
		}
		return true;*/
	}

	// ---- System Menu END ----

	private GeoPoint locationToGeoPoint(Location location) {
		GeoPoint res = new GeoPoint((int) (location.getLatitude() * 1E6), (int) (location.getLongitude() * 1E6));
		return res;
	}

	private Location geoPointToLocation(GeoPoint geoPoint) {
		Location res = new Location("Converter");
		res.setLatitude(geoPoint.getLatitudeE6() / 1E6);
		res.setLongitude(geoPoint.getLongitudeE6() / 1E6);
		return res;
	}

	private String convertPointToLocation(GeoPoint point) {
		String address = null;
		Geocoder geoCoder = new Geocoder(getBaseContext(), Locale.ENGLISH);// Locale.getDefault()
		try {
			List<Address> addresses = geoCoder.getFromLocation(point.getLatitudeE6() / 1E6, point.getLongitudeE6() / 1E6, 1);
			if (addresses.size() > 0) {
				address = "";
				for (int index = 0; index < addresses.get(0).getMaxAddressLineIndex(); index++)
					address += addresses.get(0).getAddressLine(index) + " ";
			}
		} catch (IOException e) {
			Log.e("convertPointToLocation", "Could not get location address: " + e.getMessage());
		}
		return address;
	}

	private class GPSLocationListener implements LocationListener {
		public void onLocationChanged(Location location) {
			if (location != null) {

				int accuracy = (int)location.getAccuracy();
				int accuracyThreshold = Settings.getGPSAccuracyThreshold();
				if(accuracy > accuracyThreshold) {
					txtAccuracy.setTextColor(Color.RED);
				} else {
					txtAccuracy.setTextColor(Color.BLACK);
				}
				txtAccuracy.setText(String.valueOf(accuracy) + "m");

				currentLocation = location;
				GeoPoint point = locationToGeoPoint(location);

				// add marker
				List<Overlay> listOfOverlays = mapView.getOverlays();
				MapGestureDetectorOverlay mapOverlay = (MapGestureDetectorOverlay) listOfOverlays.get(0);
				mapOverlay.setPointToDraw(point);

				mapController.animateTo(point);
				mapView.invalidate();
			}
		}

		public void onProviderDisabled(String provider) {
			Log.i("GPS status", "Main activity ProviderDisabled: " + provider);
			if(!isGPSWariningIssued) {
				new AlertDialog.Builder(PositionerActivity.this).setTitle(PositionerActivity.this.getText(R.string.onGPSDisabledWarningTitle).toString())
				.setMessage(PositionerActivity.this.getText(R.string.onGPSDisabledWarningText).toString())
				.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
						isGPSWariningIssued = true;
						dialog.cancel();
					}
				}).show();
			}
		}

		public void onProviderEnabled(String provider) {
			Log.i("GPS status", "Main activity ProviderEnabled: " + provider);
		}

		public void onStatusChanged(String provider, int status, Bundle extras) {
			Log.i("GPS status", "Main activity Status of provider " + provider + " has changed to " + status);
		}
	}

	class MapGestureDetectorOverlay extends Overlay implements OnGestureListener {
		private GeoPoint pointToDraw;
		// private boolean isMoving;
		// ----
		private GestureDetector gestureDetector;
		private OnGestureListener onGestureListener;

		public MapGestureDetectorOverlay() {
			gestureDetector = new GestureDetector(this);
		}

		public MapGestureDetectorOverlay(OnGestureListener onGestureListener) {
			this();
			setOnGestureListener(onGestureListener);
		}

		@Override
		public boolean onTouchEvent(MotionEvent event, MapView mapView) {
			if (gestureDetector.onTouchEvent(event)) {
				return true;
			}
			/*
			 * // ---when user lifts his finger--- if (event.getAction() ==
			 * MotionEvent.ACTION_UP) { pointToDraw =
			 * mapView.getProjection().fromPixels((int) event.getX(), (int)
			 * event.getY()); currentAddress =
			 * convertPointToLocation(pointToDraw); if (!isMoving) { long
			 * downPressed = event.getDownTime(); long upPressed =
			 * event.getEventTime(); if (upPressed - downPressed > 1000L) { //
			 * This is a long tap.
			 * currentLocation.setLatitude(pointToDraw.getLatitudeE6() / 1E6);
			 * currentLocation.setLongitude(pointToDraw.getLongitudeE6() / 1E6);
			 * openContextMenu(mapView); } else {
			 * Toast.makeText(getBaseContext(), currentAddress,
			 * Toast.LENGTH_SHORT).show(); } } else { isMoving = false; } } if
			 * (event.getAction() == MotionEvent.ACTION_MOVE) { isMoving = true;
			 * }
			 */
			return false;
		}

		@Override
		public boolean onDown(MotionEvent e) {
			if (onGestureListener != null) {
				return onGestureListener.onDown(e);
			}
			return false;
		}

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
			if (onGestureListener != null) {
				return onGestureListener.onFling(e1, e2, velocityX, velocityY);
			}
			return false;
		}

		@Override
		public void onLongPress(MotionEvent event) {
			if (onGestureListener != null) {
				onGestureListener.onLongPress(event);
			}
			pointToDraw = mapView.getProjection().fromPixels((int) event.getX(), (int) event.getY());
			currentAddress = convertPointToLocation(pointToDraw);
			if (currentAddress != null) {
				Utils.toast(getApplicationContext(), currentAddress, Toast.LENGTH_LONG);
			}
		}

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
			if (onGestureListener != null) {
				onGestureListener.onScroll(e1, e2, distanceX, distanceY);
			}
			return false;
		}

		@Override
		public void onShowPress(MotionEvent e) {
			if (onGestureListener != null) {
				onGestureListener.onShowPress(e);
			}
		}

		@Override
		public boolean onSingleTapUp(MotionEvent event) {
			if (onGestureListener != null) {
				onGestureListener.onSingleTapUp(event);
			}
			pointToDraw = mapView.getProjection().fromPixels((int) event.getX(), (int) event.getY());
			currentAddress = convertPointToLocation(pointToDraw);
			currentLocation.setLatitude(pointToDraw.getLatitudeE6() / 1E6);
			currentLocation.setLongitude(pointToDraw.getLongitudeE6() / 1E6);
			openContextMenu(mapView);
			return false;
		}

		public boolean isLongpressEnabled() {
			return gestureDetector.isLongpressEnabled();
		}

		public void setIsLongpressEnabled(boolean isLongpressEnabled) {
			gestureDetector.setIsLongpressEnabled(isLongpressEnabled);
		}

		public OnGestureListener getOnGestureListener() {
			return onGestureListener;
		}

		public void setOnGestureListener(OnGestureListener onGestureListener) {
			this.onGestureListener = onGestureListener;
		}

		// ----
		void setPointToDraw(GeoPoint point) {
			pointToDraw = point;
		}

		@Override
		public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) {
			super.draw(canvas, mapView, shadow);
			for(POI poi: Settings.getPOIs()) {
				drawOnMap(canvas, locationToGeoPoint(poi.getCoordinates()), poi.getResourceDrawable());
			}
			if(pointToDraw != null) {
				drawOnMap(canvas, pointToDraw, R.drawable.target_red);
				// convert point to pixels
				Point screenPts = new Point();
				mapView.getProjection().toPixels(pointToDraw, screenPts);

				// add marker
				Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.target_red);
				canvas.drawBitmap(bmp, screenPts.x, screenPts.y - 24, null);
				return true;
			} else {
				return false;
			}
		}

		private void drawOnMap(Canvas canvas, GeoPoint coords, int resourceDrawable) {
			// convert point to pixels
			Point screenPts = new Point();
			mapView.getProjection().toPixels(coords, screenPts);

			// add marker
			Bitmap bmp = BitmapFactory.decodeResource(getResources(), resourceDrawable);
			canvas.drawBitmap(bmp, screenPts.x, screenPts.y - 24, null);
		}

		/*
		 * @Override public boolean onTap(GeoPoint p, MapView mapView) {
		 * //pointToDraw = mapView.getProjection().fromPixels((int)
		 * event.getX(), (int) event.getY()); pointToDraw = p; currentAddress =
		 * convertPointToLocation(p);
		 * currentLocation.setLatitude(pointToDraw.getLatitudeE6() / 1E6);
		 * currentLocation.setLongitude(pointToDraw.getLongitudeE6() / 1E6);
		 * openContextMenu(mapView); return true; }
		 */
	}
}